#pragma once

#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"
#include <vector>
#include <queue>


//使用全局的const而不使用宏的原因：
//1.且宏不会检查代码错误，只是替换，但是const会编译报错。
//2.使用大量宏，容易造成编译时间久，且每次都需要重新替换。
const int default_capacity = 5;


//定义一个线程池类：要实现 线程数组 和 任务队列
//和线程数组有关的函数：1.创建线程并加入线程池（线程池的启动）2.线程池的销毁 3.线程等待函数
//和任务队列有关的函数：1.接收任务，并保存到任务队列。2.线程执行函数（在里面派发任务）
//构造函数：初始化锁和条件变量、线程池容量
//析构函数：释放锁和条件变量、释放每个new出来的线程
template<class T>
class ThreadPool
{
//0.单例模式下，禁止外部调用 构造、拷贝构造、赋值(都设为私有)
private:
    //0.1将构造设为私有（单例不是禁用构造，而是私有不允许调用构造）
    //1.线程池启动，即创建一批线程并添加到线程池。
    ThreadPool(int cap = default_capacity)
    :capacity_(cap)
    //,tp_(cap) //这里是开辟cap个未初始化的空间，push_back往后面加。
    {
        //初始化一把锁和条件变量。
        pthread_mutex_init(&lock_, nullptr);
        pthread_cond_init(&cond_, nullptr);
        //创建一批线程
        for(int i = 0; i < cap; ++i)
        {
            //创建线程并添加到线程池
            //tp_.push_back(new Thread(i, thread_action, ));
            
            //1.创建线程
            Thread* t = new Thread(i, thread_action, this);
            //2.将线程尾插到线程池容器中（如果使用了初始化，就是在初始化的空间后面尾插）
            tp_.push_back(t); 
            cout << "线程：" << t->getname() << ",已放入线程池。" << endl;

            //打印日志：
            //c_str() 函数可以将 const string* 类型 转化为 const char* 类型
            //logMessage(DEBUG, "线程：%s,已放入线程池。\n", (t->getname()).c_str());
        }
        cout << "线程池启动完成，线程池中线程数量为：" << tp_.size() << endl;
        //打印日志：
        //logMessage(DEBUG, "线程池启动完成，线程池中线程数量为：%d\n", tp_.size());
    }
    //【注】=delete
    //在C++11中，=delete关键字用于显式地禁用某些函数。
    //=delete用于删除一个类中的特定函数，即明确告诉编译器不要生成该函数。
    //0.2禁用拷贝构造
    ThreadPool(const ThreadPool<T> &other) = delete;
    //0.3禁用赋值
    const ThreadPool<T> & operator = (const ThreadPool<T> &other) = delete;
    
public:
    //1.获得一个懒汉式的单例
    //单例模式的类 只能通过静态的获取方法来获取对象的指针。
    //这里设置为静态方法是因为？
    static ThreadPool<T> *getThreadPool(int cap = default_capacity)
    {
    //注意：静态成员函数不能访问非静态成员变量
        //基于双重判空的线程安全的单例模式：
        //在外面再判断一次类是否已创建对象的好处是：
        //如果对象已经创建了，其他线程就不用再执行加锁解锁了，能少做很多无用功，减少资源浪费。
        if(nullptr == tp_ptr)
        {
            lockGuard lockguard(&mutex);//加锁，进行线程保护
            if(nullptr == tp_ptr)//判断类是否已创建对象
            {
                tp_ptr = new ThreadPool<T>(cap);
            }

            //RAII风格的加锁方式，离开代码块会自动释放锁。
        }
        
        return tp_ptr; 
    }
    //2.释放线程池
    ~ThreadPool()
    {
        cout << "准备释放线程池。" << endl;
        //1.释放每个new出来的线程
        for(auto &iter : tp_)
        {
            cout << "正在释放线程：" << iter->getname() << endl;
            delete iter;
        }
        cout << "所有线程已释放，线程池释放成功！" << endl;
        
        //2.释放锁和条件变量
        pthread_mutex_destroy(&lock_);
        pthread_cond_destroy(&cond_);
    }

    //线程池要对外提供的方法：
    //1.线程池启动：创建线程并添加到线程池
    //这里我做了些改动：直接把启动加入到构造函数中了，构造一个线程池对象既是启动。
    void run()
    {
    }

    //2.接收任务，并保存到任务队列。
    void pushTask(const T &task)
    {
        //2.1生产者往线程池中添加任务（要访问临界资源：任务队列，所以要加锁。）
        lockGuard lockguard(&lock_);
        task_queue_.push(task);
        //2.2接收到任务，通过条件变量唤醒一个线程
        pthread_cond_signal(&cond_);
    }

    //3.等待线程池中所有线程退出
    //能将等待子线程退出函数放到析构函数中吗？
    //最好不要，等待子线程函数最好是能被主动调用。
    void tp_jion()
    {
        for(auto &iter : tp_)
        {
            iter->p_jion();
        }
    }
    
    //4.所有线程都要执行的方法(在这里面派发任务)
    //这里就是线程拿到任务的地方（也是消费者消费的过程）
    static void* thread_action(void* arg)
    {
        //4.1根据传入的数据获取到：线程名 + 线程池
        //获取线程名。(用于打印提示信息)
        ThreadData *td = (ThreadData *)arg;
        //获取到线程池对象。(这样才能拿到任务)
        ThreadPool<T> *tp = (ThreadPool<T> *)td->tp_;

        //4.2 线程从线程池中的任务队列中获取任务（多线程访问临界资源要加锁）
        while(true)
        {
            sleep(1);
            //1.加锁
            lockGuard lockguard(&(tp->lock_));
            //2.使用条件变量，让任务队列为空时，线程进入等待。
            while((tp->task_queue_).empty())
            {
                pthread_cond_wait(&(tp->cond_), &(tp->lock_));
            }
            //3.线程进入临界资源，且任务队列不为空，线程执行任务
            //3.1从任务队列中获取任务(获取完后要从队列中弹出)
            T t = (tp->task_queue_).front();
            (tp->task_queue_).pop();
            //3.2执行任务
            t(td->name_);
        }

        return nullptr;
    }

private:
    std::vector<Thread*> tp_; //线程池
    int capacity_; //线程池容量
    std::queue<T> task_queue_; //任务队列，用来保存任务（要给线程派发任务，就必须先保存任务）
    pthread_mutex_t lock_; //保护临界资源：任务队列的锁
    pthread_cond_t cond_;  //任务队列为空时，让线程等待的条件变量。

    //单例模式
    //1.静态的线程池对象指针
    static ThreadPool<T> *tp_ptr;
    //2.静态的线程池对象指针（静态成员函数只能访问静态变量，所以只能再定义一把锁）
    static pthread_mutex_t mutex;
    //静态成员函数只能访问静态变量，不能访问普通成员变量
    //普通成员函数可访问静态成员变量、也可以访问非经常成员变量
};

//单例模式懒汉实现方式
//1.先定义好一个静态的线程池对象指针
template<typename T>
ThreadPool<T> *ThreadPool<T>::tp_ptr = nullptr;
//2.先定义好一个静态的锁
template<typename T>
pthread_mutex_t ThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;